use std::{cell::RefCell, path::PathBuf, rc::Rc};

use windows::Win32::{
	Foundation::{BOOL, HMODULE, HWND, LPARAM, TRUE, WPARAM},
	System::{
		LibraryLoader::{GetModuleFileNameW, GetModuleHandleW},
		Threading::GetCurrentThreadId,
	},
	UI::WindowsAndMessaging::{
		DispatchMessageW, EnumWindows, GetMessageW, PostQuitMessage, SendMessageW, TranslateMessage, MSG, WM_QUIT, WM_USER,
	},
};

use super::Application;

mod proxy;
pub use proxy::*;

#[derive(Clone, Copy)]
pub struct AppRef<'a>(pub &'a HMODULE);
impl<'a> types::AppRef for AppRef<'a> {
	type Proxy = AppProxy;
	fn exit(&self) {
		unsafe { PostQuitMessage(0) };
	}
	fn proxy(&self) -> AppProxy {
		let thread_id = unsafe { GetCurrentThreadId() };
		AppProxy(thread_id)
	}
}

pub struct AppHandle(pub HMODULE);
impl AppHandle {
	pub fn singleton() -> Self {
		let instance = unsafe { GetModuleHandleW(None) }.unwrap();
		AppHandle(instance)
	}
	pub unsafe fn run(&self) {
		CACHE.with_borrow(|vars| {
			if let Some(rep) = vars.gate.as_ref() {
				rep.on_launch(AppRef(&self.0))
			}
		});
		let mut message = MSG::default();
		while GetMessageW(&mut message, None, 0, 0).into() {
			if message.message == WM_USER {
				unsafe extern "system" fn enum_func(hwnd: HWND, lparam: LPARAM) -> BOOL {
					SendMessageW(hwnd, WM_USER, WPARAM(0), lparam);
					TRUE
				}
				let _ = EnumWindows(Some(enum_func), message.lParam);
				continue;
			}
			_ = TranslateMessage(&message);
			DispatchMessageW(&message);
			if message.message == WM_QUIT {
				CACHE.with_borrow(|vars| {
					if let Some(rep) = vars.gate.as_ref() {
						rep.on_terminate(AppRef(&self.0))
					}
				});
				break;
			}
		}
	}
}

pub struct AppVars {
	gate: Option<Rc<dyn types::AppRep<Application>>>,
	path: RefCell<Option<PathBuf>>,
}

thread_local! {
	static CACHE:RefCell<AppVars> = RefCell::new(AppVars {
		gate:None,
		path:Default::default(),
	});
}
pub fn app_path() -> Option<PathBuf> {
	if let Some(path) = CACHE.with_borrow(|vars| vars.path.borrow().as_ref().cloned()) {
		return Some(path);
	} else {
		const LEN: usize = 2048;
		let mut filename = [0u16; LEN];
		let len = unsafe { GetModuleFileNameW(None, &mut filename) } as usize;
		if len >= LEN {
			return None;
		}
		#[cfg(target_os = "windows")]
		{
			use std::os::windows::prelude::OsStringExt;
			let path = std::ffi::OsString::from_wide(&filename[..len]).into_string().ok();
			let path: Option<PathBuf> = path.map(|v| v.into());
			let path = path.map(|v| v.parent().map(|v| v.to_path_buf())).flatten();
			CACHE.with_borrow_mut(|vars| {
				*vars.path.borrow_mut() = path.clone();
			});
			if let Some(path) = path {
				return Some(path);
			}
		}
	};
	None
}
pub(crate) fn init_app(rep: impl types::AppRep<Application>) {
	CACHE.with_borrow_mut(|vars| {
		vars.gate = Some(Rc::new(rep));
	});
}
pub(crate) fn with_app(fun: impl FnOnce(AppRef<'_>)) {
	let app = AppHandle::singleton();
	fun(AppRef(&app.0))
}
